#pragma once
#include <boost/program_options.hpp>
#include <memory>
#include <iostream>

namespace po = boost::program_options;
class Opts
{
  public:
    static Opts& Instance()
    {
        static Opts inst;
        return inst;
    }

    void SetDescription(const std::string& desc)
    {
        description_ = desc;
    }

    template <typename... Args>
    Opts& AddOption(Args&&... args)
    {
        if (!desc_)
        {
            desc_.reset(new po::options_description(description_));
        }
        desc_->add_options()(std::forward<Args>(args)...);
        return *this;
    }

    void Parse(const int argc, const char* const argv[])
    {
        if (parsed_)
            return;

        try
        {
            po::store(po::parse_command_line(argc, argv, *desc_), vm_);
        }
        catch (boost::program_options::invalid_command_line_syntax& e)
        {
            std::cerr << e.what() << std::endl;
            Usage();
            exit(1);
        }
        po::notify(vm_);
        if (vm_.size() == 0 || vm_.count("help"))
        {
            Usage();
            exit(1);
        }
        parsed_ = true;
    }

    template <typename T>
    const T& Get(const std::string& key) const
    {
        return vm_[key].as<T>();
    }

    ~Opts()
    {
    }

  private:
    Opts()
    {
    }

    void Usage()
    {
        std::cerr << *desc_ << std::endl;
    }

  private:
    std::string description_{"No description"};
    std::unique_ptr<po::options_description> desc_{nullptr};
    po::variables_map vm_;
    bool parsed_{false};
};